#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "reg24le1.h"
#include "tgtdefs.h"
#include "nRF24L.h"
#include "nrfdbg.h"
#include "nrfutils.h"
#include "matrix.h"
#include "layers.h"
#include "sleeping.h"
#include "keycode.h"
#include "rf_protocol.h"
#include "rf_ctrl.h"
#include "ctrl_settings.h"

void init_hw(void)
{
	// rows P0.0 P0.1 P0.2 P0.6
	// cols P1.2 P1.3 P1.4 P1.5 P1.6

	P1CON = 0b01010010;		// turn on pull-ups
	P1CON = 0b01010011;
	P1CON = 0b01010100;
	P1CON = 0b01010101;
	P1CON = 0b01010110;

#ifdef DBG_MODE
	dbgInit();

	BLACK	= 0;
	BROWN	= 0;
	RED		= 0;
	ORANGE	= 0;
#endif

	// turn off the LED
	P04 = 0;

	init_sleep();
	
	matrix_init();
	rf_ctrl_init();
}

bool send_text(const char* msg, bool wait_for_finish)
{
	// this message id is used to avoid presenting the same package to the dongle in case dongle
	// received the message, but the keyboard did not receive the ACK
	static uint8_t msg_id = 1;

	uint16_t msglen = strlen(msg);
	uint8_t chunklen, msg_bytes_free;

	rf_msg_text_t txt_msg;
	txt_msg.msg_type = MT_TEXT;

	// send the message in chunks of MAX_TEXT_LEN
	while (msglen)
	{
		// flush the ACK payload
		rf_ctrl_process_ack_payloads(&msg_bytes_free, NULL);

		// copy a chunk of the message
		chunklen = msglen > MAX_TEXT_LEN ? MAX_TEXT_LEN : msglen;
		memcpy(txt_msg.text, msg, chunklen);

		msglen -= chunklen;
		msg += chunklen;

		// query the free space of the buffer on the dongle to see if
		// it is large enough to store the next chunk

		for (;;)
		{
			// send an empty text message; this causes the dongle to respond with ACK payload
			// that contains the number of bytes available in the dongle's text buffer
			if (!rf_ctrl_send_message(&txt_msg, 2))		// 1 byte for the message type ID, 1 for the msg_id
				return false;
			
			rf_ctrl_process_ack_payloads(&msg_bytes_free, NULL);
			
			// enough space in the dongle buffer?
			if (msg_bytes_free > chunklen + 1)
				break;
				
			sleep_regret(1000);	// doze off a little; roughly 30ms
		}
		
		// set the message id and send it on it's way
		msg_id = msg_id == 0xff ? 1 : msg_id + 1;
		txt_msg.msg_id = msg_id;
		if (!rf_ctrl_send_message(&txt_msg, chunklen + 2))
			return false;
	}

	// flush the ACK payload(s)
	rf_ctrl_process_ack_payloads(NULL, NULL);

	// wait for the buffer on the dongle to become empty
	// this will ensure that all the keystrokes are sent to the host and that subsequent
	// keystrokes we're sending won't mess up the text we want output at the host
	if (wait_for_finish)
	{
		uint8_t msg_bytes_capacity = 0;
		do {
			if (!rf_ctrl_send_message(&txt_msg, 2))
				return false;

			rf_ctrl_process_ack_payloads(&msg_bytes_free, &msg_bytes_capacity);
		} while (msg_bytes_free == 0  ||  msg_bytes_free != msg_bytes_capacity);
	}
	
	return true;
}

// returns false if we should enter the menu, true if we should lock the keyboard
bool process_normal(void)
{
	bool exit_to_menu = false;
	bool are_all_keys_up;
	bool ret_val = false;
	uint8_t row, col;
	uint8_t num_keys;

	do {
		rf_msg_key_state_report_t report;

		wait_for_matrix_change();
		
		set_active_layer();

		// make a key state report
		report.msg_type = MT_KEY_STATE;
		report.modifiers = 0;
		report.consumer = 0;

		num_keys = 0;

		are_all_keys_up = true;
		
		// check the keys
		for (row = 0; row < NUM_ROWS; ++row)
		{
			for (col = 0; col < NUM_COLS; ++col)
			{
				if (is_pressed_matrix(row, col))
				{
					uint8_t keycode = get_keycode(row, col);

					are_all_keys_up = false;
					
					if (keycode == KC_IMNU)
						exit_to_menu = true;
					else if (IS_MOD(keycode))
						report.modifiers |= _BV(keycode - KC_LCTRL);
					else if (IS_KEY(keycode)  &&  num_keys < MAX_KEYS)
						report.keys[num_keys++] = keycode;
				}
			}
		}

		// send the report and wait for ACK
		if (!rf_ctrl_send_message(&report, num_keys + 3))
			return true;

		// flush the ACK payloads
		rf_ctrl_process_ack_payloads(NULL, NULL);
		
	} while (!exit_to_menu  ||  !are_all_keys_up);
	
	return ret_val;
}

// returns the battery voltage in 10mV units
// for instance: get_battery_voltage() returning 278 equals a voltage of 2.78V
uint16_t get_battery_voltage(void)
{
	uint16_t res;
	
	// config the ADC

	ADCCON3 = 0b01100000;	// 8 bits, right justify
	ADCCON2 = 0b00000111;	// longest input acquisition window
							// 6us ADC power-down delay

	// start
	ADCCON1 = 0b10111000;	// power up ADC
							// 1/3 VDD
							// internal 1.2V reference
			
	// wait for the conversion to finish
	delay_us(3);	// wait for busy bit to stabilize
	while (ADCCON1 & 0x40)
		;
		
	res = ADCDATL;
	
	return (res * 72) / 51;
}

// makes a battery voltage string in the 2.34V format
// the buff buffer has to be at least 6 bytes long
void get_battery_voltage_str(char* buff)
{
	uint16_t voltage = get_battery_voltage();
	buff[0] = '0' + voltage / 100;
	buff[1] = '.';
	buff[2] = '0' + (voltage / 10) % 10;
	buff[3] = '0' + voltage % 10;
	buff[4] = 'V';
	buff[5] = '\0';
}

// returns the the number of the first key pressed and relased
// the key number is in format:
// 0xRC where R is row and C is col
uint8_t get_key_input(void)
{
	uint8_t row, col;
	uint8_t ret_val = 0;
	
	wait_for_all_keys_up();
	wait_for_key_down();

	// scan for the key pressed
	for (row = 0; row < NUM_ROWS; ++row)
	{
		for (col = 0; col < NUM_COLS; ++col)
		{
			if (is_pressed_matrix(row, col))
				ret_val = (row << 4) | col;
		}
	}
	
	wait_for_all_keys_up();
	
	return ret_val;
}

bool process_menu(void)
{
	// print the main menu
	uint8_t keynum;
	char* pEnd;
	__xdata char string_buff[64];

	do {
		// welcome & version
		if (!send_text("\x01"	// translates to Ctrl-A on the dongle
						"OneHand wireless\n"
						"firmware build " __DATE__ " " __TIME__ "\n"
						"battery voltage: ", false))
			return true;
			
		get_battery_voltage_str(string_buff);
		if (!send_text(string_buff, false))
			return true;

		// RF stats
		if (!send_text("\nRF packet stats (total/retransmit/lost): ", false))
			return true;
		
		_ultoa(rf_packets_total, string_buff, 10);
		pEnd = strchr(string_buff, '\0');
		*pEnd++ = '/';
		
		_ultoa(arc_total, pEnd, 10);
		pEnd = strchr(string_buff, '\0');
		*pEnd++ = '/';

		_ultoa(plos_total, pEnd, 10);
		if (!send_text(string_buff, false))
			return true;

		// output the time since reset
		{
			uint16_t days;
			uint8_t hours, minutes, seconds;
			get_time(&days, &hours, &minutes, &seconds);
			if (!send_text("\nkeyboard's been on for ", false))
				return true;
			
			string_buff[0] = '\0';
			if (days > 0)
			{
				_itoa(days, string_buff, 10);
				strcat(string_buff, " days ");
			}

			if (hours > 0  ||  days > 0)
			{
				_itoa(hours, strchr(string_buff, 0), 10);
				strcat(string_buff, " hours ");
			}

			pEnd = strchr(string_buff, 0);

			_itoa(minutes, pEnd, 10);
			strcat(string_buff, " minutes ");

			pEnd = strchr(string_buff, 0);
			
			_itoa(seconds, pEnd, 10);
			strcat(string_buff, " seconds (");
			
			pEnd = strchr(string_buff, 0);
			_ultoa(get_seconds32(), pEnd, 10);
			
			strcat(string_buff, " sec)\n");
		}
		
		if (!send_text(string_buff, false))
			return true;

		send_text("\n\nplease choose:\n00 - change output power (current ", false);
		switch (get_nrf_output_power())
		{
		case vRF_PWR_M18DBM:	send_text("-18", false); 	break;
		case vRF_PWR_M12DBM:	send_text("-12", false); 	break;
		case vRF_PWR_M6DBM:		send_text("-6", false); 	break;
		case vRF_PWR_0DBM:		send_text("0", false); 		break;
		}

		send_text("dBm)\n01 - lock keyboard\n04 - exit menu\n\n", true);
		
		keynum = get_key_input();

		if (keynum == 0x00)
		{
			send_text("select power:\n00 0dBm\n01 -6dBm\n02 -12dBm\n03 -18dBm\n", true);

			keynum = get_key_input();
			if (keynum == 0x00)		set_nrf_output_power(vRF_PWR_0DBM);
			if (keynum == 0x01)		set_nrf_output_power(vRF_PWR_M6DBM);
			if (keynum == 0x02)		set_nrf_output_power(vRF_PWR_M12DBM);
			if (keynum == 0x03)		set_nrf_output_power(vRF_PWR_M18DBM);
		} else if (keynum == 0x01) {
			send_text("locking not yet implemented!", true);
			keynum = get_key_input();
		}

	} while (keynum != 0x04);
	
	send_text("\n\nexiting the menu, you can type now\n", true);
	
	return false;
}

void main(void)
{
	init_hw();
	
	for (;;)
	{
		if (!process_normal())
			process_menu();
	}
}
